﻿using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Collections;
using System.Xml.Serialization;
using System.IO;
using System.Diagnostics;

using ShootSearch.Util;
using ShootSearch.FileSystem;
using ShootSearch.Core;

using Quartz;
using ShootSearch.Util.Serializers;
using ShootSearch.Logging;

namespace ShootSearch.Jobs
{
    public abstract class InputOutputJob: SequenceJob
    {

        public const string INPUT_PATH = "InputPath";
        public const string INPUT_FSID = "InputFSID";
        public const string JOB_PROGRESS = "Progress.xml";

        public static Dictionary<string, InputOutputJob> JobList = new Dictionary<string, InputOutputJob>();

        protected InputOutputJobConfig inputOutputConfig;
        protected IFileSystem inputFile;
        protected Queue<InputOutputArgument> processingQueue = null;
        protected Hashtable processedQueue = null;
        protected string current = string.Empty;
        protected bool isBusy = false;
        
        public string InputOutputID { get; protected set; }

        public InputOutputJob()
        {
            processingQueue = new Queue<InputOutputArgument>();
            processedQueue = new Hashtable();
        }

        #region ISpiderMan Members

        public JobStatus Status { get; protected set; }

        public System.Data.DataTable DetailStatus { get; protected set; }

        #endregion

        #region IConfigable Members

        public override SpiderManConfig Config
        {
            get
            {
                return inputOutputConfig;
            }
            set
            {
                if (value is InputOutputJobConfig)
                {
                    base.Config = value;
                    inputOutputConfig = value as InputOutputJobConfig;
                }
            }
        }

        #endregion           
        
        #region SequenceJob
        public override void DoWork(JobExecutionContext context)
        {

            InputOutputArgument arg = InitializeArgument();

            InputOutputID = IO.FormatDirName(contextPath);

            if (JobList.ContainsKey(InputOutputID))
            {
                InputOutputJob job = JobList[InputOutputID];
                job.AddJob(arg);
                job.Run();
            }
            else
            {
                if (InitializeProgress())
                {
                    JobList.Add(InputOutputID, this);
                    AddJob(arg);
                    Run();
                }
                else
                {
                    Log.Error(Logger,"Initialize failed!");
                    return;
                }
            }

        } 
        #endregion

        #region IDisposable Members

        public override void Dispose()
        {
            if (inputFile != null) inputFile.Close();
        }

        #endregion
        
        #region InputOutpuJob
        protected virtual InputOutputArgument InitializeArgument()
        {

            if (inputOutputConfig == null) return null;
            object path = context.MergedJobDataMap[INPUT_PATH];
            object fsid = context.MergedJobDataMap[INPUT_FSID];

            if (path == null && inputOutputConfig.InputArgument!=null) path = inputOutputConfig.InputArgument.Path;
            if (fsid == null && inputOutputConfig.InputArgument != null) fsid = inputOutputConfig.InputArgument.FSID;
            if (path == null)
            {
                return null;
            }
            if (fsid == null) fsid = string.Empty;

            return new InputOutputArgument(path.ToString(), fsid.ToString());

        }

        protected void AddJob(InputOutputArgument arg)
        {
            if (arg == null) return;
            if (string.IsNullOrEmpty(arg.FSID))
            {
                List<string> fsids = inputFile.ListAllFSIDs(arg.Path);
                foreach (string item in fsids)
                {
                    InputOutputArgument argp = new InputOutputArgument(arg.Path, item);
                    processingQueue.Enqueue(argp);
                }
            }
            else
            {
                processingQueue.Enqueue(arg);
            }
        }

        protected void Run()
        {
            if (isBusy) return;
            Status = JobStatus.Running;
            isBusy = true;
            while (isBusy && Status == JobStatus.Running)
            {
                if (processingQueue.Count == 0)
                {
                    isBusy = false;
                    break;
                }

                InputOutputArgument arg = null;

                if (!string.IsNullOrEmpty(current))
                {
                    arg = InputOutputArgument.Create(current);
                }
                else
                {
                    arg = processingQueue.Dequeue();
                    if (processedQueue.Contains(arg.ToString()))
                    {
                        current = string.Empty;
                        continue;
                    }
                    else
                    {
                        current = arg.ToString();
                        processedQueue.Add(current, null);
                    }
                }

                ProcessFSID(arg);
                Save();
                EndProcessFSID(arg);

            }

            JobList.Remove(InputOutputID);
        }

        public override void Interrupt()
        {
            base.Interrupt();
            Save();
        }

        protected virtual bool InitializeProgress()
        {
            inputFile = inputOutputConfig.Input.GetObjectWithConfig() as IFileSystem;
            if (inputFile == null) return false;
            InputOutputProgress p = InputOutputProgress.Load(contextPath + JOB_PROGRESS);
            this.current = p.Current;
            this.processedQueue = p.GetProcessed();
            this.processingQueue = p.GetQueue();
            return true;
        }

        protected virtual void Save()
        {
            InputOutputProgress p = new InputOutputProgress(current, processingQueue, processedQueue);
            p.Save(contextPath + JOB_PROGRESS);
        } 
        #endregion
        
        #region ProcessFSID
        protected virtual bool BeforeProcessFSID(InputOutputArgument arg)
        {
            inputFile.FSID = arg.FSID;
            return inputFile.CreateOpen(arg.Path);            

        }

        protected virtual void ProcessFSID(InputOutputArgument arg)
        {
            try
            {
                if (!BeforeProcessFSID(arg))
                {
                    current = string.Empty;
                    return;
                }
            }
            catch (Exception ex)
            {
                Trace.WriteLine(ex);
            } 

            List<string> urls = inputFile.ListFileNames();
            bool breaked = false;
            for (int i = 0; i < urls.Count; i++)
            {
                IFileObject doc = inputFile.Read(urls[i]);
                if (doc == null)
                {
                    ProcessNotExist(urls[i], arg);
                    continue;
                }
                ProcessDocument(doc);

                if (Status != JobStatus.Running)
                {
                    breaked = true;
                    break;
                }
            }
            if (!breaked)
            {
                current = string.Empty;
                Log.Info(Logger, "FSID process Completed!");
            }
            else
            {
                Log.Info(Logger, "FSID process intruptted!");

            }
            AfterProcessFSID(arg);
        }


        protected virtual void ProcessNotExist(string url, InputOutputArgument arg)
        {
            Log.Warn(Logger,"Does not exist {0} {1}", url, arg.ToString());
        }

        protected virtual void AfterProcessFSID(InputOutputArgument arg)
        {
            inputFile.Close();
        }

        protected virtual void EndProcessFSID(InputOutputArgument arg)
        {

            if (inputOutputConfig.LaunchNextJob == LaunchNextJobType.Manually)
            {
                Hashtable args = GetNextArgument(arg);
                IDictionaryEnumerator erator = args.GetEnumerator();
                while (erator.MoveNext())
                {
                    args.Add(erator.Key, erator.Value);
                }
                LaunchNextJob(args);
            }
        }

        protected virtual Hashtable GetNextArgument(InputOutputArgument arg)
        {
            return new Hashtable();
        }

        #endregion

        #region Abstract Methods
        protected abstract void ProcessDocument(IFileObject doc);
        
        #endregion      


      
    }


    #region InputOutputJobConfig
    [TypeConverter(typeof(Util.ExpandableConverter<InputOutputJobConfig>))]
    public class InputOutputJobConfig : SequenceJobConfig
    {
        [CategoryAttribute("Input"),
              DescriptionAttribute("Input argument, include Path & FSID")]
        public InputOutputArgument InputArgument { get; set; }
        [CategoryAttribute("Input"),
              DescriptionAttribute("Input FileSystem")]
        public AssemblyWithConfig Input { get; set; }

        public InputOutputJobConfig()
            : base()
        {
            Input = new AssemblyWithConfig(typeof(STFileSystem));
            InputArgument = new InputOutputArgument();
        }


        #region Read Write XML
        public void ReadFromXml(System.Xml.XmlReader reader)
        {
            if (reader.ReadToFollowing("UnscheduleAllTriggers"))
            {
                UnscheduleAllTriggers = reader.ReadElementContentAsBoolean();
            }

            if (reader.ReadToFollowing("LaunchNextJob"))
            {
                object enumValue = Enum.Parse(typeof(LaunchNextJobType), reader.ReadElementContentAsString(), true);
                if (enumValue != null) LaunchNextJob = (LaunchNextJobType)enumValue;
            }

            if (reader.ReadToFollowing("InputArgument"))
            {
                InputArgument = new InputOutputArgument();
                InputArgument.ReadXml(reader);
            }

            if (reader.ReadToFollowing("Input"))
            {
                Input = new AssemblyWithConfig();
                Input.ReadXml(reader);
            }


        }

        public void WriteToXml(System.Xml.XmlWriter writer)
        {
            writer.WriteStartElement("UnscheduleAllTriggers");
            writer.WriteValue(UnscheduleAllTriggers);
            writer.WriteEndElement();

            writer.WriteStartElement("LaunchNextJob");
            writer.WriteValue(LaunchNextJob.ToString());
            writer.WriteEndElement();

            writer.WriteStartElement("InputArgument");
            if (InputArgument != null) InputArgument.WriteXml(writer);
            writer.WriteEndElement();


            writer.WriteStartElement("Input");
            if (Input != null) Input.WriteXml(writer);
            writer.WriteEndElement();

            //writer.WriteStartElement("LaunchNextJobAfterSwitchFSID");
            //writer.WriteValue(LaunchNextJobAfterSwitchFSID);
            //writer.WriteEndElement();



        } 
        #endregion

    } 
    #endregion

    [TypeConverter(typeof(Util.ExpandableConverter<InputOutputArgument>))]   
    public class InputOutputArgument: IXmlSerializable
    {
        public string Path { get; set; }
        public string FSID { get; set; }

        public InputOutputArgument()
        {
            Path = string.Empty;
            FSID = string.Empty;
        }

        public InputOutputArgument(string path, string fsid)
        {
            this.Path = path;
            this.FSID = fsid;
        }

        public override string ToString()
        {
            string str = string.Empty;
            if (!string.IsNullOrEmpty(Path))
            {
                str = Path + ",";
            }

            if (!string.IsNullOrEmpty(FSID))
            {
                str += FSID;
            }
            return str;
        }

        public static InputOutputArgument Create(string current)
        {
            DirectoryInfo di = new DirectoryInfo(current);
            InputOutputArgument arg = new InputOutputArgument();
            arg.Path = IO.FormatDirName(di.Parent.FullName);
            arg.FSID = di.Name;
            return arg;
        }

        #region IXmlSerializable Members

        public System.Xml.Schema.XmlSchema GetSchema()
        {
            return new System.Xml.Schema.XmlSchema();
        }

        public void ReadXml(System.Xml.XmlReader reader)
        {
            if (reader.MoveToAttribute("Path"))
            {
                Path = reader.Value;  
            }
            if (reader.MoveToAttribute("FSID"))
            {
                FSID = reader.Value;
            }


        }

        public void WriteXml(System.Xml.XmlWriter writer)
        {
            if(!string.IsNullOrEmpty(Path))
            {
                writer.WriteStartAttribute("Path");
                writer.WriteValue(Path);
            }

            if (!string.IsNullOrEmpty(FSID))
            {
                writer.WriteStartAttribute("FSID");
                writer.WriteValue(FSID);
            }
        }

        #endregion
    }

    #region InputOutputProgress
    public class InputOutputProgress
    {
        public string Current { get; set; }
        [XmlArray]
        [XmlArrayItem("QueueItem")]
        public List<InputOutputArgument> Queue { get; set; }
        [XmlArray]
        [XmlArrayItem("ProcessedItem")]
        public List<string> Processed { get; set; }

        public InputOutputProgress()
        {
            Queue = new List<InputOutputArgument>();
            Processed = new List<string>();
        }


        public InputOutputProgress(string current, Queue<InputOutputArgument> queue, Hashtable processed)
        {
            Queue = new List<InputOutputArgument>();
            Processed = new List<string>();

            this.Current = current;
            Queue.AddRange(queue.ToArray());

            IDictionaryEnumerator erator = processed.GetEnumerator();
            while (erator.MoveNext())
            {
                Processed.Add(erator.Key.ToString());
            }
        }

        public Hashtable GetProcessed()
        {
            Hashtable ht = new Hashtable();
            foreach (string item in Processed)
            {
                ht.Add(item, null);
            }
            return ht;
        }

        public Queue<InputOutputArgument> GetQueue()
        {
            Queue<InputOutputArgument> q = new Queue<InputOutputArgument>();
            foreach (InputOutputArgument item in Queue)
            {
                q.Enqueue(item);
            }
            return q;
        }

        public void Save(string path)
        {
            XmlFormatter.ToXmlFile(this, path);
        }

        public static InputOutputProgress Load(string path)
        {
            InputOutputProgress p = new InputOutputProgress();
            p = XmlFormatter.FromFile(path, p.GetType()) as InputOutputProgress;
            if (p == null) p = new InputOutputProgress();
            return p;
        }



    } 
    #endregion
}
